package com.gitee.qdbp.base.controlling;

import java.lang.reflect.Field;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;
import com.gitee.qdbp.able.exception.ServiceException;
import com.gitee.qdbp.able.jdbc.paging.Paging;
import com.gitee.qdbp.base.annotation.DataIsolation;
import com.gitee.qdbp.base.enums.OperateType;
import com.gitee.qdbp.tools.utils.VerifyTools;

/**
 * 注解@DataIsolation的切面处理类, 实现数据隔离<br>
 * 在DAO层面切入, 以保证所有的数据库操作都会带有隔离条件<br>
 * 通过注解分析出数据隔离元数据, 供子类实现隔离逻辑<br>
 *
 * @author zhaohuihua
 * @version 170413 所有逻辑都在这一个类里面处理
 * @version 170414 拆分为三层<br>
 *          Base只负责解析出原数据<br>
 *          Simple实现按原数据获取AcceptAttrs进行控制的逻辑<br>
 *          Choose实现按字段(或数据类型)注册AcceptAttrsHandler的逻辑<br>
 */
public abstract class DataIsolationBaseControlling implements IDataIsolationControlling {

    private static final Logger log = LoggerFactory.getLogger(DataIsolationBaseControlling.class);

    /** 根据规则设置默认值或检查属性值是否在允许之列 **/
    protected abstract void handleDataIsolation(Metadata metadata, OperateType operateType) throws ServiceException;

    /** {@inheritDoc} **/
    @Override
    public void handle(Object o, OperateType operateType) throws ServiceException {
        if (o == null || o instanceof Paging) {
            return;
        }
        if (o.getClass().getName().equals("org.apache.ibatis.session.RowBounds")) {
            return;
        }

        // 扫描@DataIsolation注解
        DataIsolation annotation = AnnotationUtils.findAnnotation(o.getClass(), DataIsolation.class);
        if (annotation != null && annotation.enable()) {
            Metadata metadata = findMetadata(o, annotation);
            if (metadata != null) { // 根据规则设置默认值或检查属性值是否在允许之列
                handleDataIsolation(metadata, operateType);
            }
        }
    }

    /** {@inheritDoc} **/
    @Override
    public void handle(Object o, OperateType operateType, String field, String... fields) throws ServiceException {
        VerifyTools.requireNotBlank(field, "field");
        if (o == null || o instanceof Paging) {
            return;
        }
        if (o.getClass().getName().equals("org.apache.ibatis.session.RowBounds")) {
            return;
        }

        SimpleMetadata metadata = new SimpleMetadata();
        metadata.setObject(o);
        metadata.addField(field);
        if (VerifyTools.isNotBlank(fields)) {
            metadata.addField(fields);
        }
        // 根据规则设置默认值或检查属性值是否在允许之列
        handleDataIsolation(metadata, operateType);
    }

    /** 扫描@DataIsolation注解, 生成注解元数据 **/
    protected Metadata findMetadata(Object o, DataIsolation annotation) throws ServiceException {
        if (!annotation.enable()) {
            return null;
        }

        Class<?> clazz = o.getClass();
        String className = clazz.getName();

        String[] fields = annotation.value();
        String[] targets = annotation.target();
        if (VerifyTools.isAllBlank(fields, targets)) {
            throw new IllegalArgumentException("@DataIsolation value and target can't all is empty. " + className);
        }

        SimpleMetadata metadata = new SimpleMetadata(true);
        metadata.setObject(o);
        metadata.setFields(fields);

        // 遍历所有target指向的字段
        // 例如Update对象就会需要设置target=where
        for (String name : targets) {
            Field field = ReflectionUtils.findField(clazz, name);
            ReflectionUtils.makeAccessible(field);
            Object value = ReflectionUtils.getField(field, o);
            Class<?> type = field.getType();
            // 如果字段是泛型, 取值对象的类型作为字段类型
            if (field.getGenericType() instanceof TypeVariable) { // 泛型
                if (value == null) {
                    continue;
                } else {
                    type = value.getClass();
                }
            }
            DataIsolation targetAnnotation = AnnotationUtils.findAnnotation(type, DataIsolation.class);
            if (targetAnnotation == null) {
                // 这里如果抛异常, 就要求每个作为条件的Bean类都必须有@DataIsolation注解
                // 如果不抛异常, 后果就是配置错误难以发现
                // 如果@DataIsolation(target)指向一个不存在的字段时, 不执行数据隔离也没有异常
                // throw new IllegalArgumentException(...);
                continue;
            }
            if (!targetAnnotation.enable()) {
                continue;
            }
            if (value == null) { // 如果对象为空则创建一个对象
                if (type.isInterface()) {
                    log.warn("@DataIsolation target {}.{} can't instantiate.", className, name);
                    continue; // 接口, 无法实例化
                }
                try {
                    value = BeanUtils.instantiate(type);
                } catch (BeanInstantiationException e) {
                    log.warn("@DataIsolation target {}.{} can't instantiate. {}", className, name, e.toString());
                    continue; // 实例化对象出错(可能是没有默认构造函数等原因)
                }
                ReflectionUtils.setField(field, o, value);
            }
            Metadata sub = findMetadata(value, targetAnnotation); // 递归调用
            if (sub != null) {
                metadata.addTarget(new SimpleMetadata.Target(name, sub));
            }
        }

        return metadata;
    }

    /**
     * DataIsolation注解元数据<br>
     * 在Bean上配置需要隔离的字段, @DataIsolation({ "tenantCode", "deptCode" })<br>
     * 在Update上配置target, @DataIsolation(target = "where"), 不控制自身的字段, 只控制where这个子对象<br>
     *
     * @author zhaohuihua
     * @version 180621
     */
    public static interface Metadata {

        /** 数据隔离的目标对象 **/
        public static interface Target {

            /** 字段名 **/
            String getField();

            /** 隔离规则 **/
            Metadata getMetadata();
        }

        /** 是否启用严格模式, 如果是则当处理字段不存在时将会报错 **/
        boolean isStrict();

        /** 隔离对象 **/
        Object getObject();

        /** 处理字段 **/
        String[] getFields();

        /** 不控制自身的字段, 只控制目标子对象 **/
        Target[] getTargets();
    }

    /** DataIsolation注解元数据实现类 **/
    public static class SimpleMetadata implements Metadata {

        public static class Target implements Metadata.Target {

            private String field;
            private Metadata metadata;

            public Target(String field, Metadata metadata) {
                this.field = field;
                this.metadata = metadata;
            }

            public String getField() {
                return field;
            }

            public void setField(String field) {
                this.field = field;
            }

            public Metadata getMetadata() {
                return metadata;
            }

            public void setMetadata(Metadata metadata) {
                this.metadata = metadata;
            }

        }

        private boolean strict;
        private Object object;
        private List<String> fields;
        private List<Target> targets;

        public SimpleMetadata() {
            this.strict = true;
        }

        public SimpleMetadata(boolean strict) {
            this.strict = true;
        }

        public boolean isStrict() {
            return strict;
        }

        public void setStrict(boolean strict) {
            this.strict = strict;
        }

        public Object getObject() {
            return object;
        }

        public void setObject(Object object) {
            this.object = object;
        }

        public String[] getFields() {
            return fields == null ? null : fields.toArray(new String[0]);
        }

        public void setFields(String[] fields) {
            if (fields == null) {
                this.fields = null;
            }
            this.fields = Arrays.asList(fields);
        }

        public void addField(String... fields) {
            if (this.fields == null) {
                this.fields = new ArrayList<>();
            }
            if (fields != null && fields.length > 0) {
                this.fields.addAll(Arrays.asList(fields));
            }
        }

        public Target[] getTargets() {
            return targets == null ? null : targets.toArray(new Target[0]);
        }

        public void setTargets(Target[] targets) {
            if (targets == null) {
                this.targets = null;
            }
            this.targets = Arrays.asList(targets);
        }

        public void addTarget(Target... targets) {
            if (this.targets == null) {
                this.targets = new ArrayList<>();
            }
            if (targets != null && targets.length > 0) {
                this.targets.addAll(Arrays.asList(targets));
            }
        }
    }

}
